﻿using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using WebSocketSharp;

namespace CommonUtils
{
    /// <summary>
    /// WebSocket服务器 可用telnet命令测试：telnet+空格+IP+空格+端口
    /// </summary>
    public class WebSocketServer
    {
        public string Url;

        private WebSocketSharp.Server.WebSocketServer _server;

        public event Action<Exception, WebSocketServerSession> OnSessionError;

        public event Action<WebSocketServerSession> OnSessionConnect;

        public event Action<MessageEventArgs, WebSocketServerSession> OnSessionReceiveMessage;

        public event Action<WebSocketServerSession> OnSessionDisconnect;

        private List<WebSocketServerSession> _sessions = new List<WebSocketServerSession>();

        public WebSocketServer(string url)
        {
            Url = url;

            var separatorIndex = url.LastIndexOf('/');
            var route = url.Substring(separatorIndex);
            url = url.Substring(0, separatorIndex);

            _server = new WebSocketSharp.Server.WebSocketServer(url);

            //OnSessionDisconnect
            OnSessionDisconnect += delegate (WebSocketServerSession session) { _sessions.Remove(session); };

            //OnSessionConnect
            _server.AddWebSocketService(route, delegate (WebSocketServerSession session)
            {
                session.ErrorEvents = OnSessionError;
                session.ConnectEvents = OnSessionConnect;
                session.ReceiveEvents = OnSessionReceiveMessage;
                session.DisconnectEvents = OnSessionDisconnect;

                _sessions.Add(session);
            });

            LogUtil.PrintInfo("WebSocket服务端({0})已创建! ", Url);
        }

        public WebSocketServer(string iep, string route, bool isSecure = false)
        : this(isSecure ? "wss://" : "ws://" + iep + "/" + route) { }

        public WebSocketServer(IPEndPoint iep, string route, bool isSecure = false)
        : this(iep.ToString(), route, isSecure) { }

        public WebSocketServer(int port, string route, bool isSecure = false)
        : this("127.0.0.1:" + port, route, isSecure) { }

        public bool IsSecure => _server.IsSecure;

        public void AddCertificate(byte[] pfxBytes, string pfxPassword)
        {
            //IIS必须绑定带域名的证书
            //wss如果乱绑证书会提示net::ERR_CERT_COMMON_NAME_INVALID
            //wss如果不载入证书WebSocketServer Started Failed! Exception: There is no server certificate for secure connections.
            //X509KeyStorageFlags.MachineKeySet不能省略，否则IIS或报错，以前没有这种情况
            _server.SslConfiguration.ServerCertificate = new X509Certificate2(pfxBytes, pfxPassword, X509KeyStorageFlags.MachineKeySet);
            //_server.SslConfiguration.ClientCertificateValidationCallback = delegate { return false; };
        }

        public event Action<Exception> OnError;

        public event Action OnStart;

        public void Start()
        {
            try
            {
                _server.Start();
                LogUtil.PrintInfo("WebSocket服务端({0})已启动!", Url);
                OnStart?.Invoke();
            }
            catch (Exception ex)
            {
                LogUtil.PrintInfo("WebSocket服务端({0})启动失败! Exception:{1}", Url, ex.Message);
                OnError?.Invoke(ex);
            }
        }

        public void Open()
        => Start();

        public event Action<string, WebSocketServerSession> OnSendMessage;

        public void SendTo(string data, WebSocketServerSession session)
        {
            try
            {
                session.Send(data);
                OnSendMessage?.Invoke(data, session);
            }
            catch (Exception ex)
            {
                OnError?.Invoke(ex);
                OnSessionError?.Invoke(ex, session);
            }
        }

        public void SendTo(string data, string id)
        {
            var session = _sessions.FirstOrDefault(m => m.ID == id);
            if (session != null)
                SendTo(data, session);
        }

        public void SendTo(object data, string id)
        {
            var session = _sessions.FirstOrDefault(m => m.ID == id);
            if (session != null)
                SendTo(data, session);
        }

        public void Send(string data)
        {
            foreach (var session in _sessions)
                SendTo(data, session);
        }

        public void SendWithout(string data, WebSocketServerSession withoutSession)
        {
            foreach (var session in _sessions)
            {
                if (withoutSession == session)
                    continue;
                else
                    SendTo(data, session);
            }
        }

        public void SendTo(object data, WebSocketServerSession session)
       => SendTo(data.ToJson(true), session);

        public void Send(object data)
        => Send(data.ToJson(true));

        public void SendExclude(object data, WebSocketServerSession excludeSession)
        => SendWithout(data.ToJson(true), excludeSession);

        public event Action<byte[], WebSocketServerSession> OnSendBytes;

        public void SendTo(byte[] data, WebSocketServerSession session)
        {
            try
            {
                session.Send(data);
                OnSendBytes?.Invoke(data, session);
            }
            catch (Exception ex)
            {
                OnError?.Invoke(ex);
                OnSessionError?.Invoke(ex, session);
            }
        }

        public void Send(byte[] data)
        {
            foreach (var session in _sessions)
                SendTo(data, session);
        }

        public void SendWithout(byte[] data, WebSocketServerSession withoutSession)
        {
            foreach (var session in _sessions)
            {
                if (withoutSession == session)
                    continue;
                else
                    SendTo(data, session);
            }
        }

        public event Action OnStop;

        public void Stop()
        {
            _server.Stop();
            LogUtil.PrintInfo("WebSocket服务端({0})停止!", Url);
            OnStop?.Invoke();
        }

        public void Close() => Stop();

        public bool IsListening => _server.IsListening;

        public JObject Status
        {
            get
            {
                return new JObject {
                    { "Bind", _server.Address + ":" + _server.Port },
                    { "IsListening", _server.IsListening },
                    { "IsSecure", _server.IsSecure },
                    { "Realm", _server.Realm }
                };
            }
        }
    }
}
